package service

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"
)

// Option 典型的 Option 设计模式
type Option func(*App)

// ShutdownCallback 采用 context.Context 来控制超时，而不是用 time.After 是因为
// - 超时本质上是使用这个回调的人控制的
// - 我们还希望用户知道，他的回调必须要在一定时间内处理完毕，而且他必须显式处理超时错误
type ShutdownCallback func(ctx context.Context)

// WithShutdownCallbacks
//
//	@Description: 注册服务关闭方法
//	@param cbs
//	@return Option
func WithShutdownCallbacks(cbs ...ShutdownCallback) Option {
	return func(app *App) {
		app.cbs = cbs
	}
}

// App 这里我已经预先定义好了各种可配置字段
type App struct {
	servers []*Server

	// 优雅退出整个超时时间，默认30秒
	shutdownTimeout time.Duration

	// 优雅退出时候等待处理已有请求时间，默认10秒钟
	waitTime time.Duration
	// 自定义回调超时时间，默认三秒钟
	cbTimeout time.Duration

	cbs []ShutdownCallback
}

// NewApp 创建 App 实例，注意设置默认值，同时使用这些选项
func NewApp(servers []*Server, opts ...Option) *App {
	app := &App{
		servers:         servers,
		shutdownTimeout: 30 * time.Second, // 关闭整个服务最大时长 default 10s
		waitTime:        19 * time.Second, // 每个请求最大优雅退出等待时间 10s
		cbTimeout:       3 * time.Second,  // 自定义回调 超时时间 default 3s
	}
	// 循环options 通过调用每个 option方法，把具体处理方法注册进cbs中
	for _, opt := range opts {
		opt(app)
	}
	return app
}

// StartAndServe 你主要要实现这个方法
func (app *App) StartAndServe() {
	for _, s := range app.servers {
		srv := s
		go func() {
			if err := srv.Start(); err != nil {
				if err == http.ErrServerClosed {
					log.Printf("服务器%s已关闭", srv.name)
				} else {
					log.Printf("服务器%s异常退出", srv.name)
				}
			}
		}()
	}
	// 从这里开始优雅退出监听系统信号，强制退出以及超时强制退出。
	// 1. 定义传输退出信号的chan (ps: 这里容量2 因为要考虑同时收到了优雅和强制退出的信号所以为了防止由于优雅退出超时导致强制退出无法进入)
	quitChan := make(chan os.Signal, 2)
	// 2. 监听指定信号 (提前先定义退出信号)
	signal.Notify(quitChan, quitSignals...)
	// 3. 从退出channel取出退出信号，这里如果没信号会一直阻塞
	//(ps, 第一次收到退出信号定义为优雅退出，第二次退出信号定义为强制退出，同时如果没有人为强制退出也要监听优雅超时退出)
	<-quitChan
	go func() {
		select {
		case <-quitChan:
			log.Println("已经收到第二次退出信号这时候开始强制退出....")
			// os.Exit(0) 正常运行程序并退出程序
			// os.Exit(1) 非正常运行导致退出程序
			// ps: os.Exit 被调用的话,defer不会被执行
			os.Exit(1)
		case <-time.After(app.shutdownTimeout):
			log.Println("优雅退出超时，正在自动执行强制退出...")
			os.Exit(1)
		}
	}()
	// 优雅退出的具体步骤在 shutdown 里面实现
	// 所以你需要在这里恰当的位置，调用 shutdown

	// 如果上面的channel监听不成为goroutine shutdown永远不会被执行， 由于上面select所有情况被执行后都会执行os.Exit(1)
	// 所以上面的信号监听只能做非阻塞监听
	app.shutdown()
}

// shutdown 你要设计这里面的执行步骤。
func (app *App) shutdown() {
	log.Println("开始关闭应用，停止接收新请求")
	// 你需要在这里让所有的 server 拒绝新请求
	// 遍历所有被注册的服务，并让所有服务都开始拒绝请求
	for _, server := range app.servers {
		server.rejectReq()
	}
	log.Println("等待正在执行请求完结")
	// 在这里等待一段时间
	// 手动阻塞等待请求处理
	time.Sleep(app.waitTime)

	log.Println("开始关闭服务器")
	// 并发关闭服务器，同时要注意协调所有的 server 都关闭之后才能步入下一个阶段
	// 开始遍历所有服务，并调用其服务的关闭处理
	// 使用协程，并发关闭所有服务，且让服务之间的关闭不会被阻塞
	var serverStopWG sync.WaitGroup
	serverCnt := len(app.servers)
	serverStopWG.Add(serverCnt)
	for _, server := range app.servers {
		serverCopy := server
		go func() {
			if err := serverCopy.stop(context.Background()); err != nil {
				log.Printf("关闭服务失败%s \n", serverCopy.name)
			}
			serverStopWG.Done()
		}()
	}
	serverStopWG.Wait()
	log.Println("开始执行自定义回调")
	// 并发执行回调，要注意协调所有的回调都执行完才会步入下一个阶段
	var serverCallBackFnWG sync.WaitGroup
	serverCallBackFnWG.Add(len(app.cbs))
	for _, cb := range app.cbs {
		callbackFn := cb
		// 并发调用
		go func() {
			ctx, cancel := context.WithTimeout(context.Background(), app.cbTimeout)
			callbackFn(ctx)
			cancel()
			serverCallBackFnWG.Done()
		}()
	}
	serverCallBackFnWG.Wait()
	// 释放资源
	log.Println("开始释放资源")
	// 这一个步骤不需要你干什么，这是假装我们整个应用自己要释放一些资源
	app.close()
	return
}

func (app *App) close() {
	// 在这里释放掉一些可能的资源
	time.Sleep(time.Second)
	log.Println("应用关闭")
}

// Server 本身可以是很多种 Server，例如 http server
// 或者 RPC server
// 理论上来说，如果你设计一个脚手架的框架，那么 Server 应该是一个接口
type Server struct {
	srv  *http.Server
	name string
	mux  *serverMux
}

// serverMux 既可以看做是装饰器模式，也可以看做委托模式
type serverMux struct {
	reject bool
	*http.ServeMux
}

// ServeHTTP
//
//	@Description: 请求的处理方法
//	@receiver s
//	@param w
//	@param r
func (s *serverMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 只是在考虑到 CPU 高速缓存的时候，会存在短时间的不一致性
	if s.reject {
		w.WriteHeader(http.StatusServiceUnavailable)
		_, _ = w.Write([]byte("服务已关闭"))
		return
	}
	s.ServeMux.ServeHTTP(w, r)
}

// NewServer
//
//	@Description: 实例化http服务
//	@param name
//	@param addr
//	@return *Server
func NewServer(name string, addr string) *Server {
	mux := &serverMux{ServeMux: http.NewServeMux()}
	return &Server{
		name: name,
		mux:  mux,
		srv: &http.Server{
			Addr:    addr,
			Handler: mux,
		},
	}
}

// Handle
//
//	@Description: 设置组装路由与处理方法
//	@receiver s
//	@param pattern
//	@param handler
func (s *Server) Handle(pattern string, handler http.Handler) {
	s.mux.Handle(pattern, handler)
}

// Start
//
//	@Description: 开始监听服务
//	@receiver s
//	@return error
func (s *Server) Start() error {
	return s.srv.ListenAndServe()
}

// rejectReq
//
//	@Description: 拒绝请求，设置标记位，后续请求过来就判断标记位来拒绝处理请求
//	@receiver s
func (s *Server) rejectReq() {
	s.mux.reject = true
}

// stop
//
//	@Description: 单体服务退出
//	@receiver s
//	@param ctx
//	@return error
func (s *Server) stop(ctx context.Context) error {
	// 模拟某个服务退出超时了
	time.Sleep(time.Minute)
	log.Printf("服务器%s关闭中", s.name)
	// 在这里模拟停下服务器 真正关闭整个http服务进程
	return s.srv.Shutdown(ctx)
}
